package db

import (
	"base"
	"bitset"
	"fmt"
	"game_server/logic/db/api"
	"game_server/logic/db/event"
	"game_server/logic/db/handler"
	"game_server/logic/db/mark"
	. "game_server/logic/internal"
	"game_server/model"
	"time"
)

type dbSystem struct {
	handlers   map[string]db_handler.Interface
	markMapper map[db_mark.Enum]map[db_handler.Interface]bool
}

var system = new(dbSystem)

func init() {
	system.handlers = make(map[string]db_handler.Interface)
	system.markMapper = make(map[db_mark.Enum]map[db_handler.Interface]bool)
}

func init() {
	db_api.Register = system.Register
	db_api.Save = system.Save
	db_api.Load = system.Load
}

func init() {
	Event.Listen(db_event.Collect, system.onCollect)
}

func (*dbSystem) Register(handler db_handler.Interface) {
	if handler == nil {
		panic("handler is nil")
	}

	name := handler.Name()
	if _, exists := system.handlers[name]; exists {
		panic(fmt.Sprintf("handler %s is exists", name))
	}

	system.handlers[name] = handler
	mark := handler.DBMark()
	marks, exists := system.markMapper[mark]
	if !exists {
		marks = make(map[db_handler.Interface]bool)
		system.markMapper[mark] = marks
	}

	marks[handler] = true
}

func (*dbSystem) Save(player *model.Player, dirtyMarks *bitset.BitSet) {
	engine := PlayerMysql
	if engine == nil {
		return
	}

	defer func(begin time.Time) {
		base.Logger.Debug("save player %v to db cost time %v", player.Id(), time.Since(begin))
	}(time.Now())

	foreach := func(handlers map[db_handler.Interface]bool, args *db_handler.SaveArgs) {
		session := engine.NewSession()
		defer func() {
			if r := recover(); r != nil {
				session.Rollback()
				base.Logger.Error("save player panic, %v", r)
			}

			session.Close()
		}()

		for handler, _ := range handlers {
			result, errs := handler.OnSaveDB(session, args)
			for _, err := range errs {
				base.Logger.Error("save player failed, %v", err)
			}

			if result != nil && result.Rollback {
				session.Rollback()
				base.Logger.Error("save player rollback")
				return
			}
		}

		session.Commit()
	}

	args := new(db_handler.SaveArgs)
	args.Player = player
	for item, exists := dirtyMarks.NextSet(0); exists; item, exists = dirtyMarks.NextSet(item + 1) {
		mark := db_mark.Enum(item)
		handlers, ok := system.markMapper[mark]
		if !ok {
			base.Logger.Warning("not implement db mark %v", mark)
			continue
		}

		foreach(handlers, args)
	}
}

func (*dbSystem) Load(player *model.Player) (success bool) {
	engine := PlayerMysql
	if engine == nil {
		success = true
		return
	}

	session := engine.NewSession()
	defer func(begin time.Time) {
		if r := recover(); r != nil {
			base.Logger.Error("load player panic, %v", r)
			session.Rollback()
		} else {
			base.Logger.Debug("load player %v to db cost time %v", player.Id(), time.Since(begin))
		}

		session.Close()
	}(time.Now())

	args := new(db_handler.LoadArgs)
	args.Player = player
	for _, handler := range system.handlers {
		if _, err := handler.OnLoadDB(session, args); err != nil {
			session.Rollback()
			base.Logger.Error("load player failed, %v", err)
			return
		}
	}

	session.Commit()
	success = true

	return
}

func (*dbSystem) onCollect(player *model.Player, mark db_mark.Enum) {
	player.SetDirtyMark(uint(mark))
	if player.SaveTimer == nil {
		const delay = 3 * time.Second
		player.SaveTimer = player.AfterCall(delay, func() {
			player.SaveTimer = nil
			dirtyMark := player.ResetDirtyMark()
			if dirtyMark.None() {
				return
			}

			system.Save(player, dirtyMark)
			Event.Notify(db_event.Save, player)
		})
	}
}
